package com.xbongbong.lfasr.concurrent;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.jxc.concurrent.ConcurrentLock;
import com.xbongbong.paas.constant.MessageConstant;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.constant.TimeConstant;
import com.xbongbong.paas.enums.DelEnum;
import com.xbongbong.paas.pojo.dto.ConcurrentLockDTO;
import com.xbongbong.paas.redis.PaasRedisHelper;
import com.xbongbong.paas.redis.RedisPrefixConstant;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.Arith;
import com.xbongbong.paas.toolbox.util.CollectionsUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.pro.product.pojo.ProductStockLockPojo;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.ProductConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.domain.entity.WarehouseOristockEntity;
import com.xbongbong.saas.domain.entity.ext.AssembleEntityExt;
import com.xbongbong.saas.domain.entity.ext.InstockEntityExt;
import com.xbongbong.saas.domain.entity.ext.OutstockEntityExt;
import com.xbongbong.saas.enums.HandleProductWayEnum;
import com.xbongbong.saas.enums.SelectProductEnum;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.business.AssembleEnum;
import com.xbongbong.saas.enums.business.InstockEnum;
import com.xbongbong.saas.enums.business.OutstockEnum;
import com.xbongbong.saas.enums.business.ProductEnum;
import com.xbongbong.saas.enums.product.InstockProductEnum;
import com.xbongbong.saas.help.BatchKeyHelp;
import com.xbongbong.saas.model.AssembleModel;
import com.xbongbong.saas.model.InstockModel;
import com.xbongbong.saas.model.InventoryModel;
import com.xbongbong.saas.model.OutstockModel;
import com.xbongbong.saas.model.ProductStockModel;
import com.xbongbong.saas.model.ProductWarehouseModel;
import com.xbongbong.saas.model.TransferModel;
import com.xbongbong.saas.model.WarehouseOristockModel;
import org.apache.commons.collections4.MapUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * @author WuHB
 * @date 2021/10/20 14:49
 */
// @Aspect
@Component
public class ConcurrentLockAspect1 {

    private static final Logger LOG = LoggerFactory.getLogger(ConcurrentLockAspect1.class);

    @Resource
    private PaasRedisHelper paasRedisHelper;
    @Resource
    private InstockModel instockModel;
    @Resource
    private OutstockModel outstockModel;
    @Resource
    private AssembleModel assembleModel;
    @Resource
    private TransferModel transferModel;
    @Resource
    private InventoryModel inventoryModel;
    @Resource
    private WarehouseOristockModel warehouseOristockModel;
    @Resource
    private ProductStockModel productStockModel;
    @Resource
    private ProductWarehouseModel productWarehouseModel;
    @Resource
    private ProductRedisLock productRedisLock;

    /**
     * @Author: wujian
     * @Description: 前置通知 防止超发出库
     * @Date: 下午1:54 2021/12/7
     * @Param: []
     * @return: void
     **/
    @Before("@annotation(concurrentLock)")
    public void beforeMethod(JoinPoint joinPoint, ConcurrentLock concurrentLock) throws Throwable {
        Object[] args = joinPoint.getArgs();
        String corpid = "";
        // 本次入库的所有产品ID集合
        List<String> productIdWarehouseIdList = new ArrayList<>();
        Integer businessType = -1;
        // 操作的类型1新建 2删除
        HandleProductWayEnum handleProductWayEnum = concurrentLock.businessType();
        for (Object arg : args) {
            String jsonString = JSONObject.toJSONString(arg);
            ConcurrentLockDTO concurrentLockDTO = JSONObject.parseObject(jsonString, ConcurrentLockDTO.class);
            corpid = concurrentLockDTO.getCorpid();
            businessType = concurrentLockDTO.getBusinessType();
            XbbRefTypeEnum xbbRefTypeEnum = XbbRefTypeEnum.getByCode(businessType);
            JSONObject dataList = concurrentLockDTO.getDataList();
            JSONArray productArray = new JSONArray();
            // 新建删除需要的数据及数据处理方式不同
            // 如果不是影响成本的这些单据直接return放行
            if (!ProductConstant.EFFECT_PRODUCT_COST.contains(businessType)) {
                return ;
            }
            try {
                // 获取锁
                if (!getLock(RedisPrefixConstant.PRODUCT_STOCK_BEFORE_LOCK, corpid, 0)) {
                    throw new XbbException();
                }
                // 获取已存在redis中的产品信息
                String value = paasRedisHelper.getValue(RedisPrefixConstant.PRODUCT_STOCK_LOCK, corpid);
                Map<String, BigDecimal> productStock =  StringUtil.isEmpty(value) ? new HashMap<>() : JSONObject.parseObject(value, HashMap.class);
                if (Objects.equals(HandleProductWayEnum.ADD.getCode(), handleProductWayEnum.getCode())) {
                    Map<String, BigDecimal> saveProductStock = getSaveProductStock(concurrentLockDTO);
                    Set<String> saveProductInfo = saveProductStock.keySet();
                    Set<String> productInfo = productStock.keySet();
                    Set<String> intersection = new HashSet<>(saveProductInfo);
                    Set<String> differenceSet = new HashSet<>(saveProductInfo);
                    intersection.containsAll(productInfo);
                    differenceSet.removeAll(intersection);
                    if (CollectionsUtil.isNotEmpty(differenceSet)) {



                        for (String key : differenceSet) {
                            String[] s = key.split("_");
                            if (Objects.equals(s.length, 2)) {

                            }

                        }
                    }


                } else {

                }



            } catch (XbbException e) {
                throw e;
            } catch (Exception e) {
                throw new XbbException();
            } finally {
                paasRedisHelper.removeValue(RedisPrefixConstant.PRODUCT_STOCK_BEFORE_LOCK, corpid);
            }
        }
    }

    // 后置通知
//    @After("@annotation(concurrentLock)")
    public void afterMethod(JoinPoint joinPoint, ConcurrentLock concurrentLock) throws Throwable {
        Object[] args = joinPoint.getArgs();
        // 本次入库或者更新的的产品idList
        List<String> productIdList = new ArrayList<>();
        String corpid = "";
        for (Object arg : args) {
            String jsonString = JSONObject.toJSONString(arg);
            ConcurrentLockDTO concurrentLockDTO = JSONObject.parseObject(jsonString, ConcurrentLockDTO.class);
            corpid = concurrentLockDTO.getCorpid();
            JSONObject dataList = concurrentLockDTO.getDataList();
            Integer businessType = concurrentLockDTO.getBusinessType();
            // 如果不是影响成本的这些单据直接return放行
            if (!ProductConstant.EFFECT_PRODUCT_COST.contains(businessType)) {
                return;
            }
            XbbRefTypeEnum xbbRefTypeEnum = XbbRefTypeEnum.getByCode(businessType);
            // 操作的类型1新建 2删除
            HandleProductWayEnum handleProductWayEnum = concurrentLock.businessType();
            JSONArray productArray = new JSONArray();
            if (Objects.equals(HandleProductWayEnum.ADD.getCode(), handleProductWayEnum.getCode())) {
                handleAddData(xbbRefTypeEnum, dataList, productArray, productIdList, true);
            } else if (Objects.equals(HandleProductWayEnum.DELETE.getCode(), handleProductWayEnum.getCode())) {
                // 删除单据的id集合
                List<Long> dataIdList = concurrentLockDTO.getDataIdList();
                handleDelData(corpid, xbbRefTypeEnum, dataIdList, productIdList, false);
            }
        }
        if (CollectionsUtil.isNotEmpty(productIdList)) {
            LOG.info("要删除的数据为：" + JSONObject.toJSONString(productIdList));
            paasRedisHelper.removeValue4Set(RedisPrefixConstant.PRODUCT_COST_LOCK, corpid, productIdList);
        }
        return;
    }

    public List<ProductStockLockPojo> handleAddRedisData(XbbRefTypeEnum xbbRefTypeEnum, JSONObject dataList, JSONArray productArray, List<String> productIdWarehouseList) {
        // TODO不同单据的数据处理
        List<ProductStockLockPojo> ProductStockLockPojoList = new ArrayList<>();
        productArray = dataList.getJSONArray(OutstockEnum.PRODUCT.getAttr());
        for (int i = 0; i < productArray.size(); i++) {
            ProductStockLockPojo pojo = new ProductStockLockPojo();
            Long productId = -1L;
            Long warehouseId = -1L;
            JSONObject jsonObject = productArray.getJSONObject(i);
            productId = jsonObject.getJSONObject(InstockProductEnum.PRODUCT.getAttr()).getLong("id");
            warehouseId = jsonObject.getJSONArray(InstockProductEnum.WAREHOUSE.getAttr()).getJSONObject(0).getLong("id");
            pojo.setProductId(productId);
            pojo.setWarehouseId(warehouseId);
            Integer enableBatch = jsonObject.getInteger(ProductConstant.Enable_Batch_Shelf_Life);
            String batch = "";
            pojo.setEnableBatch(false);
            // 开启了批次的要拼接上批次
            if (Objects.equals(BasicConstant.ONE, enableBatch)) {
                batch = jsonObject.getString(InstockProductEnum.BATCH.getAttr());
                pojo.setEnableBatch(true);
                pojo.setBatch(batch);
                // 到期日期
                Long productDate = jsonObject.getLong(InstockProductEnum.EXPIRE_DATE.getAttr());
                // 保质期
                Long guaranteePeriod = jsonObject.getLong(InstockProductEnum.GUARANTEE_PERIOD.getAttr());
                if (Objects.nonNull(guaranteePeriod)) {
                    // 保质期传参为天 数据库中存储的为时间戳
                    guaranteePeriod = guaranteePeriod * 86400;
                }

                String batchKey = BatchKeyHelp.getBatchKey(productId, warehouseId, batch, productDate, guaranteePeriod);
                pojo.setProductDate(productDate);
                pojo.setGuaranteePeriod(guaranteePeriod);
                pojo.setBatchKey(batchKey);
            }
            String warehouseKey = productId + "_" + warehouseId;
            pojo.setWarehouseKey(warehouseKey);
            String productWarehouseKey = productId + "_" + warehouseId + "_" + batch;
            productIdWarehouseList.add(productWarehouseKey);
            pojo.setRedisKey(productWarehouseKey);
            Integer enableMultiUnit = jsonObject.getInteger(ProductEnum.ENABLE_MULTI_UNIT.getSaasAttr());
            Double productNum = jsonObject.getDouble(InstockProductEnum.NUM.getAttr());
            pojo.setProductNum(productNum);
            if (Objects.equals(BasicConstant.ONE, enableMultiUnit)) {
                JSONObject unitObject = jsonObject.getJSONObject(InstockProductEnum.UNIT.getAttr());
                Long businessUnit = unitObject.getLong("value");
                JSONArray unitArray = jsonObject.getJSONArray(ProductConstant.TRANSFORM_UNIT_RATE);
                for (int j = 0; j < unitArray.size(); j++) {
                    JSONObject unitPojo = unitArray.getJSONObject(j);
                    Long value = unitPojo.getLong("value");
                    if (Objects.equals(businessUnit, value)) {
                        Double rate = unitPojo.getDouble("rate");
                        productNum = Arith.mul(productNum, rate);
                        pojo.setRedisKey(productWarehouseKey);
                        pojo.setProductNum(productNum);
                        ProductStockLockPojoList.add(pojo);
                        break;
                    }
                }
            }
            ProductStockLockPojoList.add(pojo);
        }
        return ProductStockLockPojoList;
    }

    public void handleAddData(XbbRefTypeEnum xbbRefTypeEnum, JSONObject dataList, JSONArray productArray, List<String> productIdList, boolean isAfter) {
        switch (xbbRefTypeEnum) {
            case TRANSFER:
            case INVENTORY:
            case ORI_STOCK:
            case OTHER_INSTOCK:
            case PURCHASE_INSTOCK:
            case RETURNED_MATERIEL_INSTOCK:
            case PRODUCTION_INSTOCK:
            case REFUND_INSTOCK:
            case CONTRACT_OUTSTOCK:
            case ORDER_OUTSTOCK:
            case OTHER_OUTSTOCK:
            case RETURNED_PURCHASE_OUTSTOCK:
            case PRODUCTION_MATERIEL_OUTSTOCK:
            case WORK_ORDER_OUTSTOCK:
                productArray = dataList.getJSONArray(OutstockEnum.PRODUCT.getAttr());
                for (int i = 0; i < productArray.size(); i++) {
                    Long productId = -1L;
                    JSONObject jsonObject = productArray.getJSONObject(i);
                    if (isAfter) {
                        productId = jsonObject.getLong(InstockProductEnum.PRODUCT.getAttr());
                        Long refProductId = jsonObject.getLong("refProductId");
                        if (Objects.nonNull(refProductId)) {
                            productIdList.add(String.valueOf(refProductId));
                        }
                    } else {
                        productId = jsonObject.getJSONObject(InstockProductEnum.PRODUCT.getAttr()).getLong("id");
                    }
                    productIdList.add(String.valueOf(productId));
                }
                break;
            case ASSEMBLE:
                // 装配单特殊处理
                productArray = dataList.getJSONArray(AssembleEnum.OUT_PRODUCTS.getAttr());
                productArray.addAll(dataList.getJSONArray(AssembleEnum.IN_PRODUCTS.getAttr()));
                break;
            default:
                // 不是这些出入库类型的直接放行
                return;
        }
    }

    public List<String> handleDelData(String corpid, XbbRefTypeEnum xbbRefTypeEnum, List<Long> dataIdList, List<String> productIdList, boolean isBefore) {
        Map<String, Object> param = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        param.put(StringConstant.CORPID, corpid);
        param.put("idIn", dataIdList);
        // 因为盘点调拨单删除时需要先删除对应的其他出入库单所以不用处理
        switch (xbbRefTypeEnum) {
            case OTHER_INSTOCK:
            case PURCHASE_INSTOCK:
            case RETURNED_MATERIEL_INSTOCK:
            case PRODUCTION_INSTOCK:
            case REFUND_INSTOCK:
                List<InstockEntityExt> entitys = instockModel.findEntitys(param);
                if (CollectionsUtil.isNotEmpty(entitys)) {
                    for (InstockEntityExt entity : entitys) {
                        JSONObject data = entity.getData();
                        JSONArray productIds = data.getJSONArray(InstockEnum.PRODUCT.getAttr());
                        productIdList.addAll(productIds.toJavaList(String.class));
                    }
                }
                break;
            case CONTRACT_OUTSTOCK:
            case ORDER_OUTSTOCK:
            case OTHER_OUTSTOCK:
            case RETURNED_PURCHASE_OUTSTOCK:
            case PRODUCTION_MATERIEL_OUTSTOCK:
            case WORK_ORDER_OUTSTOCK:
                List<OutstockEntityExt> outstockEntityExtList = outstockModel.findEntitys(param);
                if (CollectionsUtil.isNotEmpty(outstockEntityExtList)) {
                    for (OutstockEntityExt entity : outstockEntityExtList) {
                        JSONObject data = entity.getData();
                        JSONArray productIds = data.getJSONArray(InstockEnum.PRODUCT.getAttr());
                        productIdList.addAll(productIds.toJavaList(String.class));
                    }
                }
                break;
            case ORI_STOCK:
                Map<String, Object> oriParam = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
                oriParam.put(StringConstant.CORPID, corpid);
                oriParam.put("warehouseId", dataIdList.get(0));
                if (isBefore) {
                    oriParam.put("del", DelEnum.NORMAL.getDel());
                }
                List<WarehouseOristockEntity> oristockModelEntitys = warehouseOristockModel.findEntitys(oriParam);
                if (CollectionsUtil.isNotEmpty(oristockModelEntitys)) {
                    for (WarehouseOristockEntity entity : oristockModelEntitys) {
                        JSONObject data = entity.getData();
                        JSONArray productDataArray = data.getJSONArray(InstockEnum.PRODUCT.getAttr());
                        for (int i = 0; i < productDataArray.size(); i++) {
                            JSONObject jsonObject = productDataArray.getJSONObject(i);
                            Object productId = jsonObject.get(SelectProductEnum.PRODUCT.getAttr());
                            productIdList.add(productId.toString());
                        }
                    }
                }
                break;
            case ASSEMBLE:
                // 因为装配单删除时是只传了个装配单ID 所以要根据装配单ID查询出所有装配单 再拿到所有装配单下的装配出入库单
                Map<String, Object> assembleParam = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
                assembleParam.put(StringConstant.CORPID, corpid);
                assembleParam.put("idIn", dataIdList);
                List<AssembleEntityExt> assembleModelEntitys = assembleModel.findEntitys(assembleParam);
                // 装配单先拿到装配出入库单
                if (CollectionsUtil.isNotEmpty(assembleModelEntitys)) {
                    for (AssembleEntityExt assembleModelEntity : assembleModelEntitys) {
                        JSONObject data = assembleModelEntity.getData();
                        JSONArray instockProductIdList = data.getJSONArray(AssembleEnum.IN_PRODUCTS.getAttr());
                        if (CollectionsUtil.isNotEmpty(instockProductIdList)) {
                            productIdList.addAll(instockProductIdList.toJavaList(String.class));
                        }
                        JSONArray outstockProductIdList = data.getJSONArray(AssembleEnum.OUT_PRODUCTS.getAttr());
                        if (CollectionsUtil.isNotEmpty(outstockProductIdList)) {
                            productIdList.addAll(outstockProductIdList.toJavaList(String.class));
                        }
                    }
                }
                break;
        }
        return productIdList;
    }


    /**
     * 获取锁
     *
     * @param filePrefix redis锁前缀
     * @param key        redis锁后缀
     * @param num        当前重试次数
     * @return 是否获取成功
     * @throws InterruptedException 异常
     */
    private Boolean getLock(String filePrefix, String key, Integer num) throws InterruptedException {
        // key += DateUtil.getString(new Date(), DateUtil.SDFDate);
        if (paasRedisHelper.setIfAbsent(filePrefix, key, "lock", TimeConstant.SECONDS_PER_DAY)) {
            return true;
        } else if (num < 3) {
            Thread.sleep(500);
            getLock(filePrefix, key, ++num);
        }
        return false;
    }

    private Map<String, BigDecimal> getSaveProductStock(ConcurrentLockDTO concurrentLockDTO) throws XbbException {
        Map<String, BigDecimal> saveProductStock = new HashMap<>();
        XbbRefTypeEnum xbbRefTypeEnum = XbbRefTypeEnum.getByCode(concurrentLockDTO.getBusinessType());
        switch (xbbRefTypeEnum) {
            case OTHER_INSTOCK:
            case PURCHASE_INSTOCK:
            case RETURNED_MATERIEL_INSTOCK:
            case PRODUCTION_INSTOCK:
            case REFUND_INSTOCK:
                JSONObject data = concurrentLockDTO.getDataList();
                JSONArray producJsontList = data.getJSONArray(InstockEnum.PRODUCT.getAttr());
                for (int i = 0; i < producJsontList.size(); i++) {
                    JSONObject jsonObject = producJsontList.getJSONObject(i);
                    BigDecimal num = jsonObject.getBigDecimal(InstockProductEnum.NUM.getAttr());
                    Long warehouseId = getWarehouseId(jsonObject.getJSONArray(InstockProductEnum.WAREHOUSE.getAttr()));
                    String batch = jsonObject.getString(InstockProductEnum.BATCH.getAttr());
                    Long productId = jsonObject.getJSONObject(InstockProductEnum.PRODUCT.getAttr()).getLong("id");
                    saveProductStock.put(productId +"_"+ warehouseId+ "_"+ batch, num);
                }
                break;
            case CONTRACT_OUTSTOCK:
            case ORDER_OUTSTOCK:
            case OTHER_OUTSTOCK:
            case RETURNED_PURCHASE_OUTSTOCK:
            case PRODUCTION_MATERIEL_OUTSTOCK:
            case WORK_ORDER_OUTSTOCK:

                break;
            case ORI_STOCK:
                break;
            case ASSEMBLE:
                break;
        }
        return saveProductStock;
    }

    /**
     * 获取仓库ID
     *
     * @param jsonArray 仓库信息
     * @return 仓库ID
     * @throws XbbException 异常
     */
    private Long getWarehouseId(JSONArray jsonArray) throws XbbException {
        if (CollectionsUtil.isEmpty(jsonArray)) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100002, MessageConstant.WAREHOUSE_INFO_NOT_EMPTY);
        }
        Long warehouseId = null;
        for (int j = 0; j < jsonArray.size(); j++) {
            JSONObject warehouseInfo = jsonArray.getJSONObject(j);
            if (MapUtils.isEmpty(warehouseInfo)) {
                throw new XbbException(SystemErrorCodeEnum.API_ERROR_100002, MessageConstant.WAREHOUSE_INFO_NOT_EMPTY);
            }
            warehouseId = warehouseInfo.getLong("id");
            if (Objects.isNull(warehouseId)) {
                throw new XbbException(SystemErrorCodeEnum.API_ERROR_100002, MessageConstant.WAREHOUSE_INFO_NOT_EMPTY);
            }
        }
        return warehouseId;
    }
}
